﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Manon.Core.Extensions
{
    /// <summary>
    /// 表达式树 模型转化 性能效率高于jsonconver 高于automapper 注意第一次调用由于需要生成表达式树并缓存会略微慢点，第二次调用会很快
    /// </summary>
    /// <typeparam name="TSource"></typeparam>
    /// <typeparam name="TTarget"></typeparam>
    public static class ObjectConvert<TSource, TTarget>
    {

        public readonly static Func<TSource, TTarget> MapFunc = GetMapFunc();

        public readonly static Action<TSource, TTarget> MapAction = GetMapAction();

        /// <summary>
        /// 将对象TSource转换为TTarget
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static TTarget Map(TSource source) => MapFunc(source);

        public static List<TTarget> MapList(IEnumerable<TSource> sources)
        {
            if (MapFunc == null) return null;

            return sources.Select(MapFunc).ToList();
        }



        /// <summary>
        /// 将对象TSource的值赋给给TTarget
        /// </summary>
        /// <param name="source"></param>
        /// <param name="target"></param>
        public static void Map(TSource source, TTarget target) => MapAction(source, target);



        private static Func<TSource, TTarget> GetMapFunc()
        {

            try
            {
                var sourceType = typeof(TSource);
                var targetType = typeof(TTarget);
                //Func委托传入变量
                var parameter = Expression.Parameter(sourceType, "p");

                var memberBindings = new List<MemberBinding>();

                var targetTypes = targetType.GetProperties().Where(x => x.PropertyType.IsPublic && x.CanWrite);
                foreach (var targetItem in targetTypes)
                {
                    var sourceItem = sourceType.GetProperty(targetItem.Name);

                    //判断实体的读写权限
                    if (sourceItem == null || !sourceItem.CanRead || sourceItem.PropertyType.IsNotPublic) continue;

                    //标注NotMapped特性的属性忽略转换
                    if (sourceItem.GetCustomAttribute<NotMappedAttribute>() != null) continue;

                    var sourceProperty = Expression.Property(parameter, sourceItem);
                    if (sourceProperty == null) continue;

                    if (sourceItem.PropertyType != targetItem.PropertyType)
                    {

                        //判断都是(非泛型)class
                        if (sourceItem.PropertyType.IsClass && targetItem.PropertyType.IsClass &&
                            !sourceItem.PropertyType.IsGenericType && !targetItem.PropertyType.IsGenericType)
                        {
                            var expression = GetClassExpression(sourceProperty, sourceItem.PropertyType, targetItem.PropertyType);
                            memberBindings.Add(Expression.Bind(targetItem, expression));
                            continue;
                        }

                        //集合数组类型的转换
                        if (typeof(IEnumerable).IsAssignableFrom(sourceItem.PropertyType) && typeof(IEnumerable).IsAssignableFrom(targetItem.PropertyType))
                        {
                            var expression = GetListExpression(sourceProperty, sourceItem.PropertyType, targetItem.PropertyType);
                            memberBindings.Add(Expression.Bind(targetItem, expression));
                            continue;
                        }

                        var diffProExpression = GetDiffPropertyExpression(sourceProperty, sourceItem, sourceItem.PropertyType, targetItem.PropertyType, parameter);
                        if (diffProExpression != null)
                        {
                            memberBindings.Add(Expression.Bind(targetItem, diffProExpression));
                        }
                        continue;
                    }
                    memberBindings.Add(Expression.Bind(targetItem, sourceProperty));

                }
                //创建一个if条件表达式
                var test = Expression.NotEqual(parameter, Expression.Constant(null, sourceType));// p==null;
                var ifTrue = Expression.MemberInit(Expression.New(targetType), memberBindings);
                var condition = Expression.Condition(test, ifTrue, Expression.Constant(null, targetType));

                var lambda = Expression.Lambda<Func<TSource, TTarget>>(condition, parameter);

                return lambda.Compile();
            }
            catch (System.Exception ex)
            {
                throw new System.Exception("表达式对象转换异常",ex) ;
            }
        }

        private static MethodInfo stringToDateTimeMethod => typeof(DateTime).GetMethod("Parse", new[] { typeof(string) });
        private static MethodInfo int32ToStringMethod => typeof(Int32).GetMethod("ToString", new Type[0]);
        private static MethodInfo int64ToStringMethod => typeof(Int64).GetMethod("ToString", new Type[0]);
        private static MethodInfo decimalToStringMethod => typeof(decimal).GetMethod("ToString", new Type[0]);
        private static MethodInfo doubleToStringMethod => typeof(double).GetMethod("ToString", new Type[0]);
        private static MethodInfo floatToStringMethod => typeof(float).GetMethod("ToString", new Type[0]);
        private static MethodInfo charToStringMethod => typeof(char).GetMethod("ToString", new Type[0]);
        private static MethodInfo shortToStringMethod => typeof(short).GetMethod("ToString", new Type[0]);
        private static MethodInfo byteToStringMethod => typeof(byte).GetMethod("ToString", new Type[0]);
        private static MethodInfo dateTimeTostringMethod => typeof(DateTime).GetMethod("ToString", new Type[] { typeof(string) }, new ParameterModifier[] { new ParameterModifier(1) });
        private static MethodInfo enumToIntMethod => typeof(System.Enum).GetMethod("GetHashCode");



        /// <summary>
        /// 类型为集合时赋值
        /// </summary>
        /// <param name="sourceProperty"></param>
        /// <param name="targetProperty"></param>
        /// <param name="sourceType"></param>
        /// <param name="targetType"></param>
        /// <returns></returns>
        private static Expression GetDiffPropertyExpression(Expression sourceProperty, PropertyInfo propertyInfo, Type sourceType, Type targetType, ParameterExpression parameterExpression)
        {
            try
            {

                MethodInfo methodInfo = GetMethodInfo(sourceType, targetType);
                if (methodInfo == null)
                    return null;

                var newSourceProperty = Expression.Property(parameterExpression, propertyInfo);

                if (string.Equals(targetType.Name, "String"))
                {
                    if (string.Equals(sourceType.Name, "DateTime"))
                    {
                        ConstantExpression constantReference = Expression.Constant("yyyy-MM-dd HH:mm:ss");
                        return Expression.Call(sourceProperty, methodInfo, constantReference);
                    }
                    return Expression.Call(sourceProperty, methodInfo);

                }
                if (sourceType.IsEnum && string.Equals(targetType.Name, "Int32"))
                {
                    return Expression.Call(newSourceProperty, methodInfo);
                }
                return Expression.Call(methodInfo, newSourceProperty);
            }
            catch (System.Exception ex)
            {
                throw new System.Exception($"ObjectConvertExtensions_GetDiffPropertyExpression_{sourceType.Name}转{targetType.Name}有问题!{ex.Message}");
            }

        }

        private static MethodInfo GetMethodInfo(Type sourceType, Type targetType)
        {
            MethodInfo methodInfo = null;
            switch (targetType.Name)
            {
                case "String":
                    methodInfo = GetToStringMethodInfo(sourceType);
                    break;
                case "Int32":
                    methodInfo = GetToInt32MethodInfo(sourceType);
                    break;
                case "Int64":
                    methodInfo = GetToInt64MethodInfo(sourceType);
                    break;
                case "Decimal":
                    methodInfo = GetToDecimalMethodInfo(sourceType);
                    break;
                case "Double":
                    methodInfo = GetToDoubleMethodInfo(sourceType);
                    break;
                case "Single":
                    methodInfo = GetToSingleMethodInfo(sourceType);
                    break;
                case "Byte":
                    methodInfo = GetToByteMethodInfo(sourceType);
                    break;
                case "Int16":
                    methodInfo = GetToInt16MethodInfo(sourceType);
                    break;
                case "SByte":
                    methodInfo = GetToSbyteMethodInfo(sourceType);
                    break;
                case "Char":
                    methodInfo = GetToCharMethodInfo(sourceType);
                    break;
                case "DateTime":
                    methodInfo = stringToDateTimeMethod;
                    break;

            }
            return methodInfo;
        }



        private static MethodInfo GetToSingleMethodInfo(Type sourcetype)
        {
            string sourceName = sourcetype.Name;
            if (string.Equals("String", sourceName)
                    || string.Equals("Byte", sourceName)
                    || string.Equals("SByte", sourceName)
                    || string.Equals("Short", sourceName)
                    || string.Equals("Int32", sourceName)
                    || string.Equals("Int64", sourceName)
                    || string.Equals("Decimal", sourceName)
                    || string.Equals("Double", sourceName)
                    || string.Equals("Char", sourceName))
            {
                return typeof(Convert).GetMethod("ToSingle", new[] { sourcetype });
            }
            return null;
        }

        private static MethodInfo GetToInt16MethodInfo(Type sourcetype)
        {
            string sourceName = sourcetype.Name;
            if (string.Equals("String", sourceName)
                    || string.Equals("Byte", sourceName)
                    || string.Equals("SByte", sourceName)
                    || string.Equals("Int32", sourceName)
                    || string.Equals("Int64", sourceName)
                    || string.Equals("Decimal", sourceName)
                    || string.Equals("Double", sourceName)
                    || string.Equals("Single", sourceName)
                    || string.Equals("Char", sourceName))
            {
                return typeof(Convert).GetMethod("ToInt16", new[] { sourcetype });
            }
            return null;
        }

        private static MethodInfo GetToCharMethodInfo(Type sourcetype)
        {
            string sourceName = sourcetype.Name;
            if (string.Equals("String", sourceName)
                    || string.Equals("Byte", sourceName)
                    || string.Equals("SByte", sourceName)
                    || string.Equals("Short", sourceName)
                    || string.Equals("Int32", sourceName)
                    || string.Equals("Int64", sourceName)
                    || string.Equals("Decimal", sourceName)
                    || string.Equals("Double", sourceName)
                    || string.Equals("Single", sourceName))
            {
                return typeof(Convert).GetMethod("ToChar", new[] { sourcetype });
            }
            return null;
        }

        private static MethodInfo GetToByteMethodInfo(Type sourcetype)
        {
            string sourceName = sourcetype.Name;
            if (string.Equals("Char", sourceName)
                || string.Equals("SByte", sourceName)
                || string.Equals("Short", sourceName)
                || string.Equals("String", sourceName)
                || string.Equals("Int32", sourceName)
                || string.Equals("Int64", sourceName)
                || string.Equals("Decimal", sourceName)
                || string.Equals("Double", sourceName)
                || string.Equals("Single", sourceName))
            {
                return typeof(Convert).GetMethod("ToByte", new[] { sourcetype });
            }
            return null;
        }
        private static MethodInfo GetToSbyteMethodInfo(Type sourcetype)
        {
            string sourceName = sourcetype.Name;
            if (string.Equals("Char", sourceName)
                    || string.Equals("Byte", sourceName)
                    || string.Equals("Short", sourceName)
                    || string.Equals("String", sourceName)
                    || string.Equals("Int32", sourceName)
                    || string.Equals("Int64", sourceName)
                    || string.Equals("Decimal", sourceName)
                    || string.Equals("Double", sourceName)
                    || string.Equals("Single", sourceName))
            {
                return typeof(Convert).GetMethod("ToSByte", new[] { sourcetype });
            }
            return null;
        }

        private static MethodInfo GetToDoubleMethodInfo(Type sourcetype)
        {
            string sourceName = sourcetype.Name;
            if (string.Equals("Byte", sourceName)
                    || string.Equals("SByte", sourceName)
                    || string.Equals("Short", sourceName)
                    || string.Equals("Char", sourceName)
                    || string.Equals("String", sourceName)
                    || string.Equals("Int32", sourceName)
                    || string.Equals("Int64", sourceName)
                    || string.Equals("Decimal", sourceName)
                    || string.Equals("Single", sourceName))
            {
                return typeof(Convert).GetMethod("ToDouble", new[] { sourcetype });
            }
            return null;
        }

        private static MethodInfo GetToDecimalMethodInfo(Type sourcetype)
        {
            string sourceName = sourcetype.Name;
            if (string.Equals("Byte", sourceName)
                    || string.Equals("SByte", sourceName)
                    || string.Equals("Short", sourceName)
                    || string.Equals("Char", sourceName)
                    || string.Equals("String", sourceName)
                    || string.Equals("Int32", sourceName)
                    || string.Equals("Int64", sourceName)
                    || string.Equals("Single", sourceName)
                    || string.Equals("Double", sourceName))
            {
                return typeof(Convert).GetMethod("ToDecimal", new[] { sourcetype });
            }
            return null;
        }



        private static MethodInfo GetToInt64MethodInfo(Type sourcetype)
        {
            string sourceName = sourcetype.Name;
            if (string.Equals("Byte", sourceName)
                || string.Equals("SByte", sourceName)
                || string.Equals("Short", sourceName)
                || string.Equals("Int32", sourceName)
                || string.Equals("Char", sourceName)
                || string.Equals("String", sourceName)
                || string.Equals("Single", sourceName)
                || string.Equals("Decimal", sourceName)
                || string.Equals("Double", sourceName))
            {
                return typeof(Convert).GetMethod("ToInt64", new[] { sourcetype });
            }
            return null;
        }

        private static MethodInfo GetToInt32MethodInfo(Type sourcetype)
        {
            if (sourcetype.IsEnum)
            {
                return typeof(System.Enum).GetMethod("GetHashCode", new Type[0]);
            }

            string sourceName = sourcetype.Name;
            if (string.Equals("Byte", sourceName)
                    || string.Equals("SByte", sourceName)
                || string.Equals("Short", sourceName)
                || string.Equals("Char", sourceName)
                || string.Equals("String", sourceName)
                || string.Equals("Int64", sourceName)
                || string.Equals("Decimal", sourceName)
                || string.Equals("Single", sourceName)
                || string.Equals("Double", sourceName))
            {
                return typeof(Convert).GetMethod("ToInt32", new[] { sourcetype });
            }

            return null;
        }



        private static MethodInfo GetToStringMethodInfo(Type sourcetype)
        {
            string sourceName = sourcetype.Name;
            if (string.Equals("Byte", sourceName)
                || string.Equals("SByte", sourceName)
                || string.Equals("Short", sourceName)
                || string.Equals("Char", sourceName)
                || string.Equals("Int32", sourceName)
                || string.Equals("Int64", sourceName)
                || string.Equals("Decimal", sourceName)
                || string.Equals("Double", sourceName)
                || string.Equals("Single", sourceName)
                || sourcetype.IsEnum)
            {
                return sourcetype.GetMethod("ToString", new Type[0]);
            }

            if (string.Equals("DateTime", sourcetype.Name))
            {
                return dateTimeTostringMethod;

            }
            return null;
        }

        /// <summary>
        /// 类型是clas时赋值
        /// </summary>
        /// <param name="sourceProperty"></param>
        /// <param name="targetProperty"></param>
        /// <param name="sourceType"></param>
        /// <param name="targetType"></param>
        /// <returns></returns>
        private static Expression GetClassExpression(Expression sourceProperty, Type sourceType, Type targetType)
        {
            try
            {
                //条件p.Item!=null    
                var testItem = Expression.NotEqual(sourceProperty, Expression.Constant(null, sourceType));

                //构造回调 Mapper<TSource, TTarget>.Map()
                var mapperType = typeof(ObjectConvert<,>).MakeGenericType(sourceType, targetType);
                var iftrue = Expression.Call(mapperType.GetMethod(nameof(Map), new[] { sourceType }), sourceProperty);

                var conditionItem = Expression.Condition(testItem, iftrue, Expression.Constant(null, targetType));

                return conditionItem;
            }
            catch (System.Exception ex)
            {
                throw new System.Exception($"ObjectConvertExtensions_GetClassExpression_{sourceProperty.ToString()}表达式树有问题!{ex.Message}");
            }

        }




        /// <summary>
        /// 类型为集合时赋值
        /// </summary>
        /// <param name="sourceProperty"></param>
        /// <param name="targetProperty"></param>
        /// <param name="sourceType"></param>
        /// <param name="targetType"></param>
        /// <returns></returns>
        private static Expression GetListExpression(Expression sourceProperty, Type sourceType, Type targetType)
        {
            try
            {
                //条件p.Item!=null    
                var testItem = Expression.NotEqual(sourceProperty, Expression.Constant(null, sourceType));

                //构造回调 Mapper<TSource, TTarget>.MapList()
                var sourceArg = sourceType.IsArray ? sourceType.GetElementType() : sourceType.GetGenericArguments()[0];
                var targetArg = targetType.IsArray ? targetType.GetElementType() : targetType.GetGenericArguments()[0];
                var mapperType = typeof(ObjectConvert<,>).MakeGenericType(sourceArg, targetArg);

                var mapperExecMap = Expression.Call(mapperType.GetMethod(nameof(MapList), new[] { sourceType }), sourceProperty);

                Expression iftrue;
                if (targetType == mapperExecMap.Type)
                {
                    iftrue = mapperExecMap;
                }
                else if (targetType.IsArray)//数组类型调用ToArray()方法
                {
                    iftrue = Expression.Call(mapperExecMap, mapperExecMap.Type.GetMethod("ToArray"));
                }
                else if (typeof(IDictionary).IsAssignableFrom(targetType))
                {
                    iftrue = Expression.Constant(null, targetType);//字典类型不转换
                }
                else
                {
                    iftrue = Expression.Convert(mapperExecMap, targetType);
                }

                var conditionItem = Expression.Condition(testItem, iftrue, Expression.Constant(null, targetType));

                return conditionItem;
            }
            catch (System.Exception)
            {
                throw new System.Exception($"ObjectConvertExtensions_GetListExpression_{sourceProperty.ToString()}表达式树有问题!");
            }

        }

        private static Action<TSource, TTarget> GetMapAction()
        {

            var sourceType = typeof(TSource);
            var targetType = typeof(TTarget);
            //Func委托传入变量
            var sourceParameter = Expression.Parameter(sourceType, "p");

            var targetParameter = Expression.Parameter(targetType, "t");

            //创建一个表达式集合
            var expressions = new List<Expression>();

            var targetTypes = targetType.GetProperties().Where(x => x.PropertyType.IsPublic && x.CanWrite);
            foreach (var targetItem in targetTypes)
            {
                var sourceItem = sourceType.GetProperty(targetItem.Name);

                //判断实体的读写权限
                if (sourceItem == null || !sourceItem.CanRead || sourceItem.PropertyType.IsNotPublic)
                    continue;

                //标注NotMapped特性的属性忽略转换
                if (sourceItem.GetCustomAttribute<NotMappedAttribute>() != null)
                    continue;

                var sourceProperty = Expression.Property(sourceParameter, sourceItem);
                var targetProperty = Expression.Property(targetParameter, targetItem);

                //当非值类型且类型不相同时
                if (!sourceItem.PropertyType.IsValueType && sourceItem.PropertyType != targetItem.PropertyType)
                {
                    //判断都是(非泛型)class
                    if (sourceItem.PropertyType.IsClass && targetItem.PropertyType.IsClass &&
                        !sourceItem.PropertyType.IsGenericType && !targetItem.PropertyType.IsGenericType)
                    {
                        var expression = GetClassExpression(sourceProperty, sourceItem.PropertyType, targetItem.PropertyType);
                        expressions.Add(Expression.Assign(targetProperty, expression));
                    }

                    //集合数组类型的转换
                    if (typeof(IEnumerable).IsAssignableFrom(sourceItem.PropertyType) && typeof(IEnumerable).IsAssignableFrom(targetItem.PropertyType))
                    {
                        var expression = GetListExpression(sourceProperty, sourceItem.PropertyType, targetItem.PropertyType);
                        expressions.Add(Expression.Assign(targetProperty, expression));
                    }

                    continue;
                }

                if (sourceItem.PropertyType != targetItem.PropertyType)
                {

                    //判断都是(非泛型)class
                    if (sourceItem.PropertyType.IsClass && targetItem.PropertyType.IsClass &&
                        !sourceItem.PropertyType.IsGenericType && !targetItem.PropertyType.IsGenericType)
                    {
                        var expression = GetClassExpression(sourceProperty, sourceItem.PropertyType, targetItem.PropertyType);
                        expressions.Add(Expression.Assign(targetProperty, expression));
                        continue;
                    }

                    //集合数组类型的转换
                    if (typeof(IEnumerable).IsAssignableFrom(sourceItem.PropertyType) && typeof(IEnumerable).IsAssignableFrom(targetItem.PropertyType))
                    {
                        var expression = GetListExpression(sourceProperty, sourceItem.PropertyType, targetItem.PropertyType);
                        expressions.Add(Expression.Assign(targetProperty, expression));
                        continue;
                    }

                    var diffProExpression = GetDiffPropertyExpression(sourceProperty, sourceItem, sourceItem.PropertyType, targetItem.PropertyType, sourceParameter);
                    if (diffProExpression != null)
                    {
                        expressions.Add(Expression.Assign(targetProperty, diffProExpression));
                    }
                    continue;
                }

                expressions.Add(Expression.Assign(targetProperty, sourceProperty));
            }

            //当Target!=null判断source是否为空
            var testSource = Expression.NotEqual(sourceParameter, Expression.Constant(null, sourceType));
            var ifTrueSource = Expression.Block(expressions);
            var conditionSource = Expression.IfThen(testSource, ifTrueSource);

            //判断target是否为空
            var testTarget = Expression.NotEqual(targetParameter, Expression.Constant(null, targetType));
            var conditionTarget = Expression.IfThen(testTarget, conditionSource);

            var lambda = Expression.Lambda<Action<TSource, TTarget>>(conditionTarget, sourceParameter, targetParameter);
            return lambda.Compile();
        }


        public static TTarget ConvertTo(TSource source)
        {
            return MapFunc(source);
        }

        public static List<TTarget> ConvertTo(IEnumerable<TSource> source)
        {
            if (source == null) return null;

            return source.Select(GetMapFunc()).ToList();
        }
    }

    public static class ObjectConvert
    {
        public static TTarget ConvertTo<TSource, TTarget>(this TSource source)
        {
            return ObjectConvert<TSource, TTarget>.ConvertTo(source);
        }
        public static List<TTarget> ConvertTo<TSource, TTarget>(this List<TSource> source)
        {
            if (source == null) return new List<TTarget>();

            return ObjectConvert<TSource, TTarget>.ConvertTo(source);
        }
        public static List<TTarget> ConvertTo<TSource, TTarget>(this IEnumerable<TSource> source)
        {
            if (source == null) return new List<TTarget>();

            return ObjectConvert<TSource, TTarget>.ConvertTo(source);
        }
    }



}
